package com.study.chunguard.common.utils;

import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.github.pagehelper.PageInfo;
import org.springframework.beans.BeanWrapper;
import org.springframework.beans.BeanWrapperImpl;
import org.springframework.beans.BeansException;
import org.springframework.beans.FatalBeanException;
import org.springframework.core.ResolvableType;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;

import java.beans.PropertyDescriptor;
import java.lang.annotation.*;
import java.lang.reflect.*;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import static java.lang.annotation.ElementType.FIELD;
import static org.springframework.beans.BeanUtils.getPropertyDescriptors;

/**
 * 通过借助org.springframework.beans.BeanUtils实现的Model帮助类,
 * 增加配置目标属性值的注解式属性拷贝、非空属性拷贝、单个及批量转换、
 * 提取属性的值为键值对象列表等支持
 *
 * @author zhaowenhao
 * @since 2022-12-28
 */
public class ModelUtils {

    /**
     * 用于缓存构造函数,避免每次都通过反射获取构造函数
     */
    private static final Map<Class<?>, Constructor<?>> CONSTRUCTOR_CACHE = new ConcurrentHashMap<>();


    /**
     * 使用源对象的属性创建目标对象
     *
     * @param sourceBean  源对象
     * @param targetClazz 目标实体类型
     * @param <T>         目标实体类型
     * @return 目标对象
     */
    public static <T> T copyTo(Object sourceBean, Class<T> targetClazz) {
        return Optional.ofNullable(sourceBean).map(bean -> {
            try {
                Constructor<T> constructor = getConstructor(targetClazz);
                T targetBean = constructor.newInstance();
                copyProperties(bean, targetBean);
                return targetBean;
            } catch (InstantiationException | IllegalAccessException | InvocationTargetException e) {
                throw new IllegalArgumentException("实体类转换时报错：" + e.getMessage());
            }
        }).orElse(null);
    }

    @SuppressWarnings("unchecked")
    private static <T> Constructor<T> getConstructor(Class<T> targetClazz) {
        return (Constructor<T>) CONSTRUCTOR_CACHE.computeIfAbsent(targetClazz, clazz -> {
            try {
                Constructor<?> foundConstructor = targetClazz.getDeclaredConstructor();
                foundConstructor.setAccessible(true);
                return foundConstructor;
            } catch (NoSuchMethodException e) {
                throw new IllegalArgumentException("无法获取构造函数：" + e.getMessage());
            }
        });
    }

    /**
     * 使用源对象集合的属性批量创建目标对象列表
     *
     * @param sourceBeans 源对象
     * @param targetClazz 目标实体类型
     * @param <T>         目标实体类型
     * @return 目标对象列表
     */
    public static <T> List<T> copyAllTo(Collection<?> sourceBeans, Class<T> targetClazz) {
        return convertList(sourceBeans, o -> ModelUtils.copyTo(o, targetClazz));
    }

    /**
     * 转换集合元素类型
     *
     * @param sourceCollection 源列表对象
     * @param mapper           映射器
     * @param <S>              源列表元素类型
     * @param <T>              目标列表元素类型
     * @return 转换后的列表对象
     */
    public static <S, T> List<T> convertList(Collection<S> sourceCollection, Function<S, T> mapper) {
        return sourceCollection.stream().map(mapper).collect(Collectors.toList());
    }

    /**
     * 使用 mybatis plus 源分页元素类型的分页对象创建目标分页元素类型的分页对象副本
     *
     * @param sourcePage  源分页对象
     * @param targetClazz 目标实体类型
     * @param <S>         源分页元素类型
     * @param <T>         目标分页元素类型
     * @return 目标分页对象
     */
    public static <T, S> Page<T> copyPageTo(Page<S> sourcePage, Class<T> targetClazz) {
        return convertPage(sourcePage, o -> ModelUtils.copyTo(o, targetClazz));
    }

    /**
     * 使用 page helper 源分页元素类型的分页对象创建目标分页元素类型的分页对象副本
     *
     * @param sourcePageInfo 源分页对象
     * @param targetClazz    目标实体类型
     * @param <S>            源分页元素类型
     * @param <T>            目标分页元素类型
     * @return 目标分页对象
     */
    public static <T, S> PageInfo<T> copyPageInfoTo(PageInfo<S> sourcePageInfo, Class<T> targetClazz) {
        return convertPageInfo(sourcePageInfo, o -> ModelUtils.copyTo(o, targetClazz));
    }

    /**
     * 转换 mybatis plus 分页元素类型
     *
     * @param sourcePage 源分页对象
     * @param mapper     映射器
     * @param <S>        源分页元素类型
     * @param <T>        目标分页元素类型
     * @return 转换后的分页对象
     */
    public static <S, T> Page<T> convertPage(Page<S> sourcePage, Function<S, T> mapper) {
        Page<T> targetPageInfo = new Page<>();
        copyProperties(sourcePage, targetPageInfo);
        List<T> targetRecords = convertList(sourcePage.getRecords(), mapper);
        targetPageInfo.setRecords(targetRecords);
        return targetPageInfo;
    }

    /**
     * 转换 page helper 分页元素类型
     *
     * @param sourcePageInfo 源分页对象
     * @param mapper         映射器
     * @param <S>            源分页元素类型
     * @param <T>            目标分页元素类型
     * @return 转换后的分页对象
     */
    public static <S, T> PageInfo<T> convertPageInfo(PageInfo<S> sourcePageInfo, Function<S, T> mapper) {
        PageInfo<T> targetPageInfo = new PageInfo<>();
        copyProperties(sourcePageInfo, targetPageInfo);
        List<T> targetList = convertList(sourcePageInfo.getList(), mapper);
        targetPageInfo.setList(targetList);
        return targetPageInfo;
    }

    /**
     * 复制源对象的属性到目标对象
     *
     * @param sourceBean 源对象
     * @param targetBean 目标对象
     */
    public static void copyProperties(Object sourceBean, Object targetBean) {
        copyProperties(sourceBean, targetBean, (String[]) null);
    }

    /**
     * 选择性复制源对象的属性值，只有非空属性才被复制到目标对象
     *
     * @param sourceBean 源对象
     * @param targetBean 目标对象
     */
    public static void copyPropertiesSelective(Object sourceBean, Object targetBean) {
        copyPropertiesSelective(sourceBean, targetBean, (String[]) null);
    }

    /**
     * 选择性复制源对象的属性值，只有非空属性才被复制到目标对象
     *
     * @param sourceBean       源对象
     * @param targetBean       目标对象
     * @param ignoreProperties 忽略的属性
     */
    public static void copyPropertiesSelective(Object sourceBean, Object targetBean, String... ignoreProperties) {
        Set<String> ignorePropertiesFinal = new HashSet<>(Arrays.asList(getNullPropertyNames(sourceBean)));
        if (ignoreProperties != null) {
            ignorePropertiesFinal.addAll(Arrays.asList(ignoreProperties));
        }
        copyProperties(sourceBean, targetBean, ignorePropertiesFinal.toArray(new String[0]));
    }

    private static String[] getNullPropertyNames(Object object) {
        final BeanWrapperImpl wrapper = new BeanWrapperImpl(object);
        return Stream.of(wrapper.getPropertyDescriptors()).map(PropertyDescriptor::getName).filter(propertyName -> wrapper.getPropertyValue(propertyName) == null).toArray(String[]::new);
    }

    /**
     * Copy the property values of the given source bean into the given target bean.
     * <p>Note: The source and target classes do not have to match or even be derived
     * from each other, as long as the properties match. Any bean properties that the
     * source bean exposes but the target bean does not will silently be ignored.
     * <p>As of Spring Framework 5.3, this method honors generic type information
     * when matching properties in the source and target objects. See the
     * documentation for {@link #copyProperties(Object, Object)} for details.
     *
     * @param source           the source bean
     * @param target           the target bean
     * @param ignoreProperties array of property names to ignore
     * @throws BeansException if the copying failed
     * @see BeanWrapper
     */
    @SuppressWarnings("AlibabaAvoidComplexCondition")
    private static void copyProperties(Object source, Object target, @Nullable String... ignoreProperties) throws BeansException {

        Assert.notNull(source, "Source must not be null");
        Assert.notNull(target, "Target must not be null");

        Class<?> actualEditable = target.getClass();
        PropertyDescriptor[] targetPds = getPropertyDescriptors(actualEditable);
        List<String> ignoreList = (ignoreProperties != null ? Arrays.asList(ignoreProperties) : null);

        for (PropertyDescriptor targetPd : targetPds) {
            Method writeMethod = targetPd.getWriteMethod();
            // noinspection AlibabaAvoidComplexCondition
            if (writeMethod != null && (ignoreList == null || !ignoreList.contains(targetPd.getName()))) {
                // 定制了这里的方法，可以自定义属性的复制
                PropertyDescriptor sourcePd = getCustomPropertyDescriptor(source.getClass(), targetPd.getName());
                if (sourcePd != null) {
                    Method readMethod = sourcePd.getReadMethod();
                    if (readMethod != null) {
                        ResolvableType sourceResolvableType = ResolvableType.forMethodReturnType(readMethod);
                        ResolvableType targetResolvableType = ResolvableType.forMethodParameter(writeMethod, 0);

                        // Ignore generic types in assignable check if either ResolvableType has unresolvable generics.
                        boolean isAssignable = (sourceResolvableType.hasUnresolvableGenerics() || targetResolvableType.hasUnresolvableGenerics() ? ClassUtils.isAssignable(writeMethod.getParameterTypes()[0], readMethod.getReturnType()) : targetResolvableType.isAssignableFrom(sourceResolvableType));

                        if (isAssignable) {
                            try {
                                if (!Modifier.isPublic(readMethod.getDeclaringClass().getModifiers())) {
                                    readMethod.setAccessible(true);
                                }
                                Object value = readMethod.invoke(source);
                                if (!Modifier.isPublic(writeMethod.getDeclaringClass().getModifiers())) {
                                    writeMethod.setAccessible(true);
                                }
                                if (value instanceof String) {
                                    value = ((String) value).trim();
                                }
                                writeMethod.invoke(target, value);
                            } catch (Throwable ex) {
                                throw new FatalBeanException("Could not copy property '" + targetPd.getName() + "' from source to target", ex);
                            }
                        }
                    }
                }
            }
        }
    }

    /**
     * 提取属性的值为键值对象列表
     *
     * @param sourceBean 源对象
     * @return 键值对象列表
     */
    public static List<Map<String, Object>> extractKeyValueList(Object sourceBean) {
        ArrayList<Map<String, Object>> maps = new ArrayList<>();
        Field[] fields = sourceBean.getClass().getDeclaredFields();
        Arrays.stream(fields).filter(field -> field.isAnnotationPresent(KeyValueExtractor.class)).sorted(Comparator.comparingInt(o -> o.getAnnotation(KeyValueExtractor.class).order())).forEach(field -> {
            KeyValueExtractor annotation = field.getAnnotation(KeyValueExtractor.class);
            // 键值处理
            String keyField = annotation.keyField();
            String valueField = annotation.valueField();
            String keyFieldValue = annotation.value();
            field.setAccessible(true);
            try {
                // 必须条件组装
                Object valueFieldValue = field.get(sourceBean);
                HashMap<String, Object> map = new HashMap<>(2);
                map.put(keyField, keyFieldValue);
                map.put(valueField, valueFieldValue);
                maps.add(map);
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
        });
        return maps;
    }


    /**
     * @author zhaowenhao
     * 用于表示该属性可以提取为键值对象,如{"name":"username","value":"zhaowenhao"}
     * @since 2022-02-09
     */
    @Retention(RetentionPolicy.RUNTIME)
    @Target({FIELD})
    @Documented
    @Inherited
    public @interface KeyValueExtractor {

        /**
         * 键字段名称
         */
        String keyField() default "name";

        /**
         * 值字段名称
         */
        String valueField() default "value";

        /**
         * 键字段值
         */
        String value();

        /**
         * 顺序
         */
        int order() default 0;
    }

    // region 注解配置式的属性映射

    private static final PropertyDescriptor[] EMPTY_PROPERTY_DESCRIPTOR_ARRAY = {};

    static final ConcurrentMap<Class<?>, CachedFieldTargetResults> FIELD_MAPPER_TARGET = new ConcurrentHashMap<>(64);


    static CachedFieldTargetResults forClass(Class<?> beanClass) throws BeansException {
        CachedFieldTargetResults results = FIELD_MAPPER_TARGET.get(beanClass);
        if (results != null) {
            return results;
        }

        results = new CachedFieldTargetResults(beanClass);

        CachedFieldTargetResults existing = FIELD_MAPPER_TARGET.putIfAbsent(beanClass, results);
        return (existing != null ? existing : results);
    }

    @Nullable
    public static PropertyDescriptor getCustomPropertyDescriptor(Class<?> clazz, String propertyName) throws BeansException {
        return forClass(clazz).getPropertyDescriptor(propertyName);
    }

    public static final class CachedFieldTargetResults {

        /**
         * PropertyDescriptor objects keyed by property name String.
         */
        private final Map<String, PropertyDescriptor> propertyDescriptors;

        private CachedFieldTargetResults(Class<?> beanClass) throws BeansException {
            this.propertyDescriptors = new LinkedHashMap<>();
            PropertyDescriptor[] propertyDescriptors = org.springframework.beans.BeanUtils.getPropertyDescriptors(beanClass);
            for (PropertyDescriptor propertyDescriptor : propertyDescriptors) {
                this.propertyDescriptors.put(propertyDescriptor.getName(), propertyDescriptor);
            }
            // 额外处理注解
            for (Field declaredField : beanClass.getDeclaredFields()) {
                PropertyDescriptor pd = org.springframework.beans.BeanUtils.getPropertyDescriptor(beanClass, declaredField.getName());
                if (declaredField.isAnnotationPresent(FieldTarget.class)) {
                    FieldTarget annotation = declaredField.getAnnotation(FieldTarget.class);
                    String fieldTargetValue = annotation.value();
                    if (fieldTargetValue != null && !"".equals(fieldTargetValue)) {
                        this.propertyDescriptors.put(fieldTargetValue, pd);
                    }
                }
            }
        }

        @Nullable
        PropertyDescriptor getPropertyDescriptor(String name) {
            PropertyDescriptor pd = this.propertyDescriptors.get(name);
            if (pd == null && StringUtils.hasLength(name)) {
                // Same lenient fallback checking as in Property...
                pd = this.propertyDescriptors.get(StringUtils.uncapitalize(name));
                if (pd == null) {
                    pd = this.propertyDescriptors.get(StringUtils.capitalize(name));
                }
            }
            return pd;
        }

        PropertyDescriptor[] getPropertyDescriptors() {
            return this.propertyDescriptors.values().toArray(EMPTY_PROPERTY_DESCRIPTOR_ARRAY);
        }
    }

    /**
     * @author zhaowenhao
     * 用于表示该属性复制时的对应的目标属性名称
     * @since 2022-02-09
     */
    @Retention(RetentionPolicy.RUNTIME)
    @Target({FIELD})
    @Documented
    @Inherited
    public @interface FieldTarget {

        /**
         * 对应的目标属性名称
         */
        String value() default "";
    }
    // endregion
}
